#pragma once

#include <cppunit/config/SourcePrefix.h>
#include <iostream>
#include <string>
#include "TestProcTable.h"
#include "Utilities.h"

void ProcTableTest::setUp(){

	validNameInput.push_back("abcdef");
	validNameInput.push_back("a1b2c3d4e5f6");
	validNameInput.push_back("x");
	validNameInput.push_back("n#u3s");
	validNameInput.push_back("1@3");
	validNameInput.push_back("-9");
	validNameInput.push_back("00");
	validNameInput.push_back("1");
	validNameInput.push_back("%");
	validNameInput.push_back("~");

	invalidNameInput.push_back("");

	invalidIndexInput.push_back(0);
	invalidIndexInput.push_back(-1);
	invalidIndexInput.push_back(-1000);

	invalidStartEndInput.push_back(std::make_pair<StartEndType, StartEndType>(2,1));
	invalidStartEndInput.push_back(std::make_pair<StartEndType, StartEndType>(1,-1));
	invalidStartEndInput.push_back(std::make_pair<StartEndType, StartEndType>(0,1));
	invalidStartEndInput.push_back(std::make_pair<StartEndType, StartEndType>(-1,1));

	invalidPair = std::make_pair<StartEndType, StartEndType>(-1, -1);

	nonExistNameInput.push_back("bad");
	nonExistNameInput.push_back("13");
	nonExistNameInput.push_back("***");

	nonExistIndexInput.push_back(99);
	nonExistIndexInput.push_back(78);
	nonExistIndexInput.push_back(1234);

	unsigned int n;
	for(n=0; n<validNameInput.size(); n++){
		expectedIndexList.push_back(n+1);
		validStartEndInput.push_back(std::make_pair<StartEndType, StartEndType>(n+1,n+1));
	}

	procTb = ProcTable();
}

void ProcTableTest::tearDown(){
	validNameInput.clear();
	validStartEndInput.clear();
	nonExistIndexInput.clear();
	nonExistNameInput.clear();
	invalidIndexInput.clear();
	invalidStartEndInput.clear();
	expectedIndexList.clear();
}

// Registers the fixture into the 'registry'
CPPUNIT_TEST_SUITE_REGISTRATION( ProcTableTest );

void ProcTableTest::testInsert(Type::List::Name nameList, Type::List::Proc indexList, 
	bool pass, std::string failMsg){
		int tmpIndex, bufIndex;
		Type::Name::Proc tmpName;
		unsigned int t;
		bool tmpTF;

		for(t=0; t<nameList.size(); t++){
			tmpName = nameList.at(t);
			tmpIndex = procTb.insertProc(tmpName);

			bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
			tmpTF = tmpIndex == bufIndex;

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
		}
}

void ProcTableTest::testInsertStartEnd(Type::List::Proc procList, PairList startEndList, 
	bool pass, std::string failMsg){
		Type::Pair::StartEnd tmpPair;
		Type::Index::Proc tmpProc;
		unsigned int t, testNumber;
		bool tmpTF;

		if(procList.size() > startEndList.size()){
			testNumber = procList.size();
		}else{
			testNumber = startEndList.size();
		}

		for(t=0; t<procList.size(); t++){
			tmpProc = procList.at(Utilities::getIndexBoundedBySize(t, procList.size()));
			tmpPair = startEndList.at(Utilities::getIndexBoundedBySize(t, startEndList.size()));
			
			tmpTF = procTb.insertProcStartEnd(tmpProc, tmpPair.first, tmpPair.second);
			if(!pass){
				tmpTF = !tmpTF;
			}
			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
		}
}

void ProcTableTest::testGetIndices(Type::List::Name nameList, bool isNamePresent, Type::List::Proc indexList,
	bool pass, std::string failMsg){
		Type::List::Proc tmpProcList;
		int tmpIndex, bufIndex;
		Type::Name::Proc tmpName;
		unsigned int t;
		bool tmpTF;

		if(isNamePresent){
			for(t=0; t<nameList.size(); t++){
				tmpName = nameList.at(t);
				tmpIndex = procTb.getProcIndex(tmpName);

				bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
				tmpTF = tmpIndex == bufIndex;

				if(!pass){
					tmpTF = !tmpTF;
				}

				CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
					failMsg + " (with name present)", CPPUNIT_ASSERT(tmpTF));
			}
		}else{
			tmpProcList = procTb.getProcIndices();

			tmpTF = tmpProcList.size() == indexList.size();
			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (without name present, size different)", CPPUNIT_ASSERT(tmpTF));

			for(t=0; t<tmpProcList.size(); t++){
				tmpIndex = tmpProcList.at(t);
				bufIndex = indexList.at(Utilities::getIndexBoundedBySize(t, indexList.size()));
				tmpTF = tmpIndex == bufIndex;

				if(!pass){
					tmpTF = !tmpTF;
				}

				CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
					failMsg + " (without name present)", CPPUNIT_ASSERT(tmpTF));
			}
		}
}

void ProcTableTest::testGetNames(Type::List::Proc indexList, Type::List::Name nameList, 
	bool pass, std::string failMsg){

		int tmpIndex;
		Type::Name::Proc tmpName, bufName;
		unsigned int t;
		bool tmpTF;

		for(t=0; t<indexList.size(); t++){
			tmpIndex = indexList.at(t);
			tmpName = procTb.getProcName(tmpIndex);

			bufName = nameList.at(Utilities::getIndexBoundedBySize(t, nameList.size()));
			tmpTF = tmpName.compare(bufName) == 0;

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
		}
}

void ProcTableTest::testGetStartEnd(Type::List::Proc procList, PairList startEndList, bool pass, std::string failMsg){
	Type::Pair::StartEnd tmpPair, bufPair;
	Type::Index::Proc tmpProc;
	unsigned int t;
	bool tmpTF;

	Type::List::Proc list = procTb.getProcIndices();
	for(t=0; t<procList.size(); t++){
		tmpProc = procList.at(t);
		tmpPair = procTb.getProcStartEnd(tmpProc);

		bufPair = startEndList.at(Utilities::getIndexBoundedBySize(t, startEndList.size()));
		tmpTF = tmpPair.first == bufPair.first && tmpPair.second == bufPair.second;
		
		if(!pass){
			tmpTF = !tmpTF;
		}

		CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(failMsg, CPPUNIT_ASSERT(tmpTF));
	}
}

void ProcTableTest::testIsExists(Type::List::Name nameList, Type::List::Proc indexList, 
	bool pass, std::string failMsg){
		Type::Index::Proc tmpIndex;
		Type::Name::Proc tmpName;
		bool tmpTF;
		unsigned int t;

		for(t =0 ; t < indexList.size(); t++){
			tmpIndex = indexList.at(t);
			tmpTF = procTb.isProcExistsInProcTable(tmpIndex);

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (index existence)" ,CPPUNIT_ASSERT(tmpTF));
		}

		for(t =0 ; t < nameList.size(); t++){
			tmpName = nameList.at(t);
			tmpTF = procTb.isProcExistsInProcTable(tmpName);

			if(!pass){
				tmpTF = !tmpTF;
			}

			CPPUNIT_ASSERT_ASSERTION_PASS_MESSAGE(
				failMsg + " (name existence)" ,CPPUNIT_ASSERT(tmpTF));
		}
}

// method to test the insertion and retrieval of procedure
void ProcTableTest::testInsert(){
	std::cout<<std::endl<< "ProcTable Insert()"<<std::endl;

	// verify procedure insertion with invalid name input will return -1
	msg = "Failed at insertion of invalid proc name";
	testInsert(invalidNameInput, Type::List::Proc(invalidNameInput.size(), -1), true, msg);

	// verify procedure insertion with valid name input will not return -1
	msg = "Failed at insertion of valid proc name";
	testInsert(validNameInput, expectedIndexList, true, msg);

	// verify procedure insertion with valid name input will return same indices
	msg = "Failed at insertion of repeating valid proc name";
	testInsert(validNameInput, expectedIndexList, true, msg);

	// verify procedure start & end line insertion with 
	// valid proc index and invalid start end will return false
	msg = "Failed at insertion of invalid start end with valid proc index";
	testInsertStartEnd(expectedIndexList, invalidStartEndInput, false, msg);

	// verify procedure start & end line insertion with 
	// invalid proc index and invalid start end will return false
	msg = "Failed at insertion of invalid start end with invalid proc index";
	testInsertStartEnd(invalidIndexInput, invalidStartEndInput, false, msg);

	// verify procedure start & end line insertion with 
	// valid pro index and start end will not return false
	msg = "Failed at insertion of valid start end with valid proc index";
	testInsertStartEnd(expectedIndexList, validStartEndInput, true, msg);

	// verify procedure start & end line insertion with 
	// non existing proc index and valid start end will return false
	msg = "Failed at insertion of valid start end with not inserted proc index";
	testInsertStartEnd(nonExistIndexInput, validStartEndInput, false, msg);

	// verify procedure start & end line insertion with 
	// invalid proc index and valid start end will return false
	msg = "Failed at insertion of valid start end with invalid proc index";
	testInsertStartEnd(invalidIndexInput, validStartEndInput, false, msg);

	// verify procedure start & end line insertion with repeating start end will return false
	msg = "Failed at insertion of valid start end with valid proc index";
	testInsertStartEnd(expectedIndexList, validStartEndInput, false, msg);
}

void ProcTableTest::testGetIndices(){
	std::cout<<std::endl<< "ProcTable GetIndices()"<<std::endl;

	testInsert();

	// verify all proc indcies stored
	msg = "Failed at getting all indices";
	testGetIndices(Type::List::Name(), false, expectedIndexList, true, msg);

	// verify the correctness of proc name - index records
	msg = "Failed at getting indices from valid names";
	testGetIndices(validNameInput, true, expectedIndexList, true, msg);

	// verify that there should not be any records of invalid proc name being insereted
	msg = "Failed at getting indices from invalid names";
	testGetIndices(invalidNameInput, true, Type::List::Proc(1, -1), true, msg);

	// verify that there should not be any records of not inserted proc name
	msg = "Failed at getting indices from not inserted names";
	testGetIndices(nonExistNameInput, true, Type::List::Proc(1, -1), true, msg);
}

// method to test the correctness of the stored procedure names
void ProcTableTest::testGetNames(){
	std::cout<<std::endl<< "ProcTable GetNames()"<<std::endl;

	testInsert();

	// verify proc name retrieval with the expected proc index
	msg = "Failed at getting proc name from expected index";
	testGetNames(expectedIndexList, validNameInput, true, msg);

	// verify proc name retrieval with the invalid proc index will return empty string
	msg = "Failed at getting proc name from invalid index";
	testGetNames(invalidIndexInput, Type::List::Name(1, ""), true, msg);

	// verify proc name retrieval with the non existing proc index will return empty string
	msg = "Failed at getting proc name from not inserted index";
	testGetNames(nonExistIndexInput, Type::List::Name(1, ""), true, msg);
}

// method to test the correctness of start and end lines stored for a procedure
void ProcTableTest::testGetStartEnd(){
	std::cout<<std::endl<< "ProcTable GetStartEnd()"<<std::endl;

	testInsert();

	// verify the retrieval of start and end lines of valid procedure will always return true
	msg = "Failed at getting start and end lines of valid proc index";
	testGetStartEnd(expectedIndexList, validStartEndInput, true, msg);

	// verify the retrieval of start and end lines of invalid procedure will always return false
	msg = "Failed at getting start and end lines of invalid proc index";
	testGetStartEnd(invalidIndexInput, PairList(1, invalidPair), false, msg);

	// verify the retrieval of start and end lines of non existing procedure will always return false
	msg = "Failed at getting start and end lines of not inserted proc index";
	testGetStartEnd(nonExistIndexInput, PairList(1, invalidPair), false, msg);
}

// method to test the existence of procedure
void ProcTableTest::testIsExists(){
	std::cout<<std::endl<< "ProcTable Exist()"<<std::endl;

	testInsert();

	// verify the existence of valid proc name and expected proc indices to be stored
	msg = "Failed at checking existence of valid proc name, expected proc indcies";
	testIsExists(validNameInput, expectedIndexList, true, msg);

	// verify the existence of invalid proc name and indices not to be stored
	msg = "Failed at checking existence of invalid proc name and indcies";
	testIsExists(invalidNameInput, invalidIndexInput, false, msg);

	// verify the existence of not inserted proc name and indices not to be stored
	msg = "Failed at checking existence of not inserted proc name and indcies";
	testIsExists(nonExistNameInput, nonExistIndexInput, false, msg);
}